#include "RakNet\\RakNetworkFactory.h"
#include "RakNet\\RakPeerInterface.h"
#include "RakNet\\MessageIdentifiers.h"
#include "RakNet\\RakSleep.h"
#include "RakNet\\GetTime.h"
#include "RakNet\\BitStream.h"


#pragma comment(lib, "ws2_32.lib")
#pragma comment(lib, "RakNetDLL.lib")
#pragma comment(lib, "RakNetLibStatic.lib")

#pragma warning(disable: 4244)
#pragma warning(disable: 4018)
#pragma warning(disable: 4996)

/*#include "MySQL++\\mysql++.h"

#pragma comment(lib, "mysqlpp_d.lib")
#pragma comment(lib, "mysqlpp_excommon.lib")*/


#include <iostream>
#include <string>
#include <iostream>
#include <fstream>
#include <map>
#include <string>
#include <ctime>
#include <conio.h>
using namespace std;

#include "DataManager.hpp"

#define ENABLE_MODIFY_PACKET_KICK // if a packet is modified then the sender will automatically be kicked
//#define SIMULATE_NETWORK_CONDITIONS

namespace Server {


struct Message
{
	unsigned char messageId;
	char str[32];

};

struct HugeMessage
{
	unsigned char messageId;
	char str[2048];
};

struct MediumMessage
{
    unsigned char messageId;
    char str[256];
};

enum MessageTypes
{
	ID_UPDATE_STATS = ID_USER_PACKET_ENUM + 1,
	ID_UPDATE_LOCATION,
	ID_GET,
	ID_RESYNC,
	ID_ADD,

	ID_SAVE,
	ID_LOAD,
	
	ID_LEAVE,

	ID_CHAT,
	ID_SET_NAME,

	ID_CONNECTED_SUCCESS
};

void RunServer();
void RunClient();
void RunSQL();

string ToLongString(int val);
string ToLongString(float val);

const int TOTAL_CONNECTIONS = 10;
const int PORT = 100;
const char* SERVER_TO_CLIENT_FREQUENCY_TABLE = "s2cft";
const char* CLIENT_TO_SERVER_FREQUENCY_TABLE = "c2sft";

int main()
{

	char runWhat;
	cout << "Server (t/f): ";
	cin >> runWhat;
	cout << endl;

	if(runWhat == 't')
		RunServer();
	else if (runWhat == 'f')
		RunClient();


	return 0;
}




void RunServer()
{
	DataManager data;
	map<SystemAddress, string> IPToUsername;

	cout << "Server being created...\n";
	RakPeerInterface *server = RakNetworkFactory::GetRakPeerInterface();
	SocketDescriptor s(PORT, 0);

	server->Startup(TOTAL_CONNECTIONS, 0, &s, 1);
	server->SetMaximumIncomingConnections(TOTAL_CONNECTIONS);
	server->SetOccasionalPing(true);
	server->SetCompileFrequencyTable(true);

	FILE *serverToClientFrequencyTableFilePointer, *clientToServerFrequencyTableFilePointer;
	serverToClientFrequencyTableFilePointer = fopen(SERVER_TO_CLIENT_FREQUENCY_TABLE, "rb");
	if (serverToClientFrequencyTableFilePointer==0)
	{
		printf("Can't open %s\n", SERVER_TO_CLIENT_FREQUENCY_TABLE);
	}
	else
	{
		unsigned int frequencyData[256];
		int numRead;
		numRead=(int)fread(frequencyData, sizeof(unsigned int), 256, serverToClientFrequencyTableFilePointer);
        if (numRead != 256)
			puts("Error reading data");
		else
		{
			server->GenerateCompressionLayer(frequencyData, false); // server to client is output for the server so the last parameter is false
			puts("Compression layer generated for server to client data");
		}

		fclose(serverToClientFrequencyTableFilePointer);
	}

	clientToServerFrequencyTableFilePointer = fopen(CLIENT_TO_SERVER_FREQUENCY_TABLE, "rb");
	if (clientToServerFrequencyTableFilePointer==0)
	{
		printf("Can't open %s\n", CLIENT_TO_SERVER_FREQUENCY_TABLE);
	}
	else
	{
		unsigned int frequencyData[256];
		int numRead;
		numRead=(int)fread(frequencyData, sizeof(unsigned int), 256, clientToServerFrequencyTableFilePointer);
		if (numRead != 256)
			puts("Error reading data");
		else
		{
			server->GenerateCompressionLayer(frequencyData, true); // client to server is input for the server so the last parameter is true
			puts("Compression layer generated for server to client data");
		}

		fclose(clientToServerFrequencyTableFilePointer);
	}



	#ifdef SIMULATE_NETWORK_CONDITIONS
	server->ApplyNetworkSimulator(150, 500, 1500);
	#endif

	cout << "Server created...\n";
	while(true)
	{
		Packet *packet = server->Receive();
		if(!packet)
		{
			RakSleep(30);
			continue;
		}
		else if(packet->data[0] == ID_NEW_INCOMING_CONNECTION)
		{
			cout << "Client has connected from " << packet->systemAddress.ToString(true) << endl;
			MediumMessage msg;
			strcpy(msg.str, "You have connected");
			msg.messageId = ID_CONNECTED_SUCCESS;
			server->Send((char*) &msg, (const int) sizeof(msg.str), HIGH_PRIORITY, RELIABLE, 0, UNASSIGNED_SYSTEM_ADDRESS, true);
		}

		else if(packet->data[0] == ID_DISCONNECTION_NOTIFICATION)
		{
			cout << "Client " << packet->systemAddress.ToString(true) << " has disconnected normally" << endl;
			MediumMessage msg;
			strcpy(msg.str, "A client has disconnected normally");
			msg.messageId = ID_CHAT;
			server->Send((char*) &msg, (const int) sizeof(msg.str), HIGH_PRIORITY, RELIABLE, 0, UNASSIGNED_SYSTEM_ADDRESS, true);
		}

		else if(packet->data[0] == ID_MODIFIED_PACKET)
		{
			cout << "Client " << packet->systemAddress.ToString(true) << " has modified a packet" << endl;
			#ifdef ENABLE_MODIFY_PACKET_KICK
			server->CloseConnection(packet->systemAddress, true);
			MediumMessage msg;
			strcpy(msg.str, "A client has been kicked for cheating");
			msg.messageId = ID_CHAT;
			server->Send((char*) &msg, (const int) sizeof(msg.str), HIGH_PRIORITY, RELIABLE, 0, UNASSIGNED_SYSTEM_ADDRESS, true);
			#endif
		}
		else if(packet->data[0] == ID_CONNECTION_LOST)
		{
			cout << "Client " << packet->systemAddress.ToString(true) << " has disconnected abnormally" << endl;
			MediumMessage msg;
			strcpy(msg.str, "A client has disconnected abnormally");
			msg.messageId = ID_CHAT;
			server->Send((char*) &msg, (const int) sizeof(msg.str), HIGH_PRIORITY, RELIABLE, 0, UNASSIGNED_SYSTEM_ADDRESS, true);
		}
		else if(packet->data[0] == ID_LEAVE)
		{
			server->CloseConnection(packet->systemAddress, true);
			printf("Server: Compression ratio=%f. Decompression ratio=%f\n", server->GetCompressionRatio(), server->GetDecompressionRatio());
			/*Message msg;
			strcpy(msg.str, "A client has disconnected normally");
			msg.messageId = CHAT_BOX;
			server->Send((char*) &msg, (const int) sizeof(msg.str), HIGH_PRIORITY, RELIABLE, 0, UNASSIGNED_SYSTEM_ADDRESS, true);*/
		}
		/*else if(packet->data[0] == ID_PONG)
		{
			RakNetTime time, dataLength;
			RakNet::BitStream pong( packet->data+1, sizeof(RakNetTime), false);
			pong.Read(time);
			dataLength = packet->length - sizeof(unsigned char) - sizeof(RakNetTime);
			printf("ID_PONG from SystemAddress:%u:%u.\n", packet->systemAddress.binaryAddress, packet->systemAddress.port);
			printf("Time is %i\n",time);
			printf("Ping is %i\n", (unsigned int)(RakNet::GetTime()-time));
			printf("Data is %i bytes long.\n", dataLength);
			if (dataLength > 0)
				printf("Data is %s\n", packet->data+sizeof(unsigned char)+sizeof(RakNetTime));

		}*/


		else if(packet->data[0] == ID_CHAT)
		{
			// Message now holds what we want to broadcast
			char message[2048];
			HugeMessage msg;
			sprintf(message, "%s", packet->data + 1);

			msg.messageId = ID_CHAT;
			strcpy(msg.str, IPToUsername[packet->systemAddress].c_str());
			strcat(msg.str, message);

			server->Send((const char*)&msg, (const int) sizeof(msg.str), HIGH_PRIORITY, RELIABLE, 0, packet->systemAddress, true);
		}
		else if(packet->data[0] == ID_SET_NAME)
		{
			int pos = 1;
			string username;
			ParseData(pos, username, packet->data);
			username.append(": ");
			IPToUsername.insert(std::pair<SystemAddress, string>(packet->systemAddress, username));
			IPToUsername[packet->systemAddress] = username;
		}

		else if(packet->data[0] == ID_UPDATE_LOCATION)
		{
			// UPDATE LOCATION  ||||	type	 id    x    y   xVel  yVel
			//cout << "UPDATE_UNIT_LOCATION message recieved" << endl;
			data.ChangeUnitLocation(packet->data);

			char message[32];
			sprintf(message, "%s", packet->data);
			/*Message msg;
			sprintf(msg.str, "%s", packet->data);
			msg.messageId = ID_UPDATE_LOCATION;*/
			server->Send((const char*)message, (const int) sizeof(message), HIGH_PRIORITY, RELIABLE, 0, UNASSIGNED_SYSTEM_ADDRESS, true);

		}

		else if(packet->data[0] == ID_ADD)
		{
			//cout << "addUnit call recieved ( " << packet->data + 1 << " )" << endl;
			int id = data.AddUnit(packet->data);

			Message msg;
			sprintf(msg.str, "%s", data.GetUnitLocation(id).c_str());
			msg.messageId = ID_UPDATE_LOCATION;
			server->Send((const char*)&msg, (const int) sizeof(msg.str), HIGH_PRIORITY, RELIABLE, 0, UNASSIGNED_SYSTEM_ADDRESS, true);
		}

		else if(packet->data[0] == ID_GET)
		{
			string netData = data.GetUnitLocation(packet->data);

			if(netData != "invalidID")
			{
				Message msg;
				msg.messageId = ID_GET;
				strcpy(msg.str, netData.c_str());
				server->Send((char*) &msg, sizeof(msg.str), HIGH_PRIORITY, RELIABLE, 0, packet->systemAddress, false);
			}
			/*else
			{
				Message msg;
				msg.messageId = getUnit;
				strcpy(msg.str, "Invalid unit ID");
				server->Send((char*) &msg, sizeof(msg.str), HIGH_PRIORITY, RELIABLE, 0, packet->systemAddress, false);
			}*/
		}

		else if(packet->data[0] == ID_SAVE)
		{
			data.SaveData(packet->data);
		}

		else if(packet->data[0] == ID_LOAD)
		{

			data.LoadData(packet->data);
		}

		else if(packet->data[0] == ID_RESYNC)
		{
			cout << "RESYNC message recieved from " << packet->systemAddress.ToString(true) << endl;

			Message msg;
			msg.messageId = ID_UPDATE_LOCATION;
			for(int i = 0; i < (data.GetTotalUnits()); ++i)
			{
				strcpy(msg.str, data.GetUnitLocation(i).c_str());
				server->Send((char*) &msg, sizeof(msg.str), HIGH_PRIORITY, RELIABLE, 0, packet->systemAddress, false);
			}

			cout << "RESYNC message sent" << endl;
		}


		/*else if(packet->data[0] == GENERATE_FREQUENCY_TABLES)
		{
			serverToClientFrequencyTableFilePointer = fopen(SERVER_TO_CLIENT_FREQUENCY_TABLE, "wb");
			if (serverToClientFrequencyTableFilePointer==0)
			{
				printf("Can't open %s\n", SERVER_TO_CLIENT_FREQUENCY_TABLE);
			}
			else
			{
				unsigned int frequencyData[256];

				// Get the frequency table generated during the run
				server->GetOutgoingFrequencyTable(frequencyData);


				fwrite(frequencyData, sizeof(unsigned int), 256, serverToClientFrequencyTableFilePointer);
				fclose(serverToClientFrequencyTableFilePointer);
			}

			serverToClientFrequencyTableFilePointer = fopen(CLIENT_TO_SERVER_FREQUENCY_TABLE, "wb");
			if (serverToClientFrequencyTableFilePointer==0)
			{
				printf("Can't open %s\n", SERVER_TO_CLIENT_FREQUENCY_TABLE);
			}
			else
			{
				unsigned int frequencyData[256];

				// Get the frequency table generated during the run
				server->GetOutgoingFrequencyTable(frequencyData);


				fwrite(frequencyData, sizeof(unsigned int), 256, serverToClientFrequencyTableFilePointer);
				fclose(serverToClientFrequencyTableFilePointer);
			}
		}*/

		else
			cout << packet->data << endl;

	}


	server->Shutdown(300);
	RakNetworkFactory::DestroyRakPeerInterface(server);
}


void RunClient()
{
	cout << "Client being created...\n";
	int CLIENT_PORT = 0;

	RakPeerInterface *client = RakNetworkFactory::GetRakPeerInterface();
	SocketDescriptor s(CLIENT_PORT, 0);

	client->Startup(1, 0, &s, 1);

	string IP = "127.0.0.1";
	cout << "IP (127.0.0.1 is default (d for default)): ";
	cin >> IP;

	if(IP == "d")
		IP = "127.0.0.1";

	client->Connect(IP.c_str(), PORT, 0, 0, 0);
	cout << "Client created...\n\n";

	#ifdef SIMULATE_NETWORK_CONDITIONS
	client->ApplyNetworkSimulator(150, 500, 1500);
	#endif

	bool connected = false;
	SystemAddress server;



	while(true)
	{

		Packet *packet = client->Receive();

		if(connected)
		{

					/*int cmdInt = 0;
					string cmd;
					cmd = "";
					// id
					cmd.append("0");
					cmd.append("$");

					// x
					cmdInt = 265;
					cmd.append(NumToString(cmdInt).c_str());
					cmd.append("$");

					// y
					cmdInt = 2532;
					cmd.append(NumToString(cmdInt).c_str());
					cmd.append("$");

					string temp = "3.135";

					// xVel
					cmd += temp;
					cmd.append("$");

					// yVel
					cmd += temp;
					cmd.append("$");



					Message message;
					message.messageId = UPDATE_UNIT_LOCATION;
					strcpy(message.str, cmd.c_str());
					client->Send((char*) &message, sizeof(message.str), HIGH_PRIORITY, RELIABLE, 0, server, false);*/





			if(kbhit())
			{
				//cout << endl;
				string cmd;
				int cmdInt;
				float cmdFloat;

				cin >> cmd;

				if(cmd == "get")
				{
					cmd.clear();
					cin >> cmdInt;

					cmd.append(NumToString(cmdInt).c_str());
					cmd.append("$");


					Message message;
					message.messageId = ID_GET;
					strcpy(message.str, cmd.c_str());
					cout << "\nmessage sent to server: " << (char*) &message << endl;

					client->Send((char*) &message, sizeof(message.str), HIGH_PRIORITY, RELIABLE, 0, server, false);
				}

				else if(cmd == "username")
				{
					cmd.clear();
					cin >> cmd;
					cmd.append("$");
					Message message;
					message.messageId = ID_SET_NAME;
					strcpy(message.str, cmd.c_str());
					client->Send((char*) &message, sizeof(message.str), HIGH_PRIORITY, RELIABLE, 0, server, false);
				}

				else if(cmd == "leave")
				{
					cmd.append("$");
					Message msg;
					msg.messageId = ID_LEAVE;
					client->Send((char*) &msg, sizeof(msg.str), HIGH_PRIORITY, RELIABLE, 0, server, false);

				}

				else if(cmd == "save")
				{
					cmd.clear();
					cin >> cmd; // filepath
					cmd.append("$");

					Message message;
					message.messageId = ID_SAVE;
					strcpy(message.str, cmd.c_str());
					client->Send((char*) &message, sizeof(message.str), HIGH_PRIORITY, RELIABLE, 0, server, false);
				}

				else if(cmd == "load")
				{
					cmd.clear();
					cin >> cmd; // filepath
					cmd.append("$");

					Message message;
					message.messageId = ID_LOAD;
					strcpy(message.str, cmd.c_str());
					client->Send((char*) &message, sizeof(message.str), HIGH_PRIORITY, RELIABLE, 0, server, false);
				}

				else if(cmd == "add")
				{
					cmd = "";
					// x
					string temp;
					cin >> temp;
					cmd += temp;
					cmd.append("$");

					// y
					cin >> temp;
					cmd += temp;
					cmd.append("$");

					

					// xVel
					cin >> temp;
					cmd += temp;
					cmd.append("$");

					// yVel
					cin >> temp;
					cmd += temp;
					cmd.append("$");



					Message message;
					message.messageId = ID_ADD;
					strcpy(message.str, cmd.c_str());
					client->Send((char*) &message, sizeof(message.str), HIGH_PRIORITY, RELIABLE, 0, server, false);

				}

				else if(cmd == "update")
				{
					cmd = "";

					// id
					cin >> cmdInt;
					cmd.append(NumToString(cmdInt).c_str());
					cmd.append("$");

					// x
					cin >> cmdInt;
					cmd.append(NumToString(cmdInt).c_str());
					cmd.append("$");

					// y
					cin >> cmdInt;
					cmd.append(NumToString(cmdInt).c_str());
					cmd.append("$");

					string temp;

					// xVel
					cin >> temp;
					cmd += temp;
					cmd.append("$");

					// yVel
					cin >> temp;
					cmd += temp;
					cmd.append("$");


					Message message;
					message.messageId = ID_UPDATE_LOCATION;
					strcpy(message.str, cmd.c_str());
					client->Send((char*) &message, sizeof(message.str), HIGH_PRIORITY, RELIABLE, 0, server, false);
				}

				/*else if(cmd == "generate")
				{
					Message msg;
					msg.messageId = GENERATE_FREQUENCY_TABLES;
					client->Send((char*) &msg, sizeof(msg.str), HIGH_PRIORITY, RELIABLE, 0, server, false);
				}*/

				else if(cmd == "resync")
				{
					Message msg;
					msg.messageId = ID_RESYNC;
					strcpy(msg.str, "$");
					client->Send((char*) &msg, sizeof(msg.str), HIGH_PRIORITY, RELIABLE, 0, server, false);
				}

				else // cmd == chat
				{
                    char previous[2048];
                    sprintf(previous, "%s", cmd.c_str());

                    char reader[2048];
                    cin.getline(reader, 2048, '\n');

                    HugeMessage msg;
                    strcpy(msg.str, previous);
                    strcat(msg.str, reader);

                    msg.messageId = ID_CHAT;

					client->Send((char*) &msg, sizeof(msg.str), HIGH_PRIORITY, RELIABLE, 0, server, false);
				}

			}

		}

		if(!packet)
		{
			RakSleep(30);
			continue;
		}


		if(packet != NULL)
		{
			if(packet->data[0] == ID_CONNECTION_REQUEST_ACCEPTED)
			{
				server = packet->systemAddress;
				connected = true;

				cout << "Connection success!" << endl;
			}
			else if(packet->data[0] == ID_CONNECTION_ATTEMPT_FAILED)
			{
				cout << "Connection failed!" << endl;
			}

			else if(packet->data[0] == ID_PONG)
			{
				RakNetTime time, dataLength;
				RakNet::BitStream pong( packet->data+1, sizeof(RakNetTime), false);
				pong.Read(time);
				dataLength = packet->length - sizeof(unsigned char) - sizeof(RakNetTime);
				printf("ID_PONG from SystemAddress:%u:%u.\n", packet->systemAddress.binaryAddress, packet->systemAddress.port);
				printf("Time is %i\n",time);
				printf("Ping is %i\n", (unsigned int)(RakNet::GetTime()-time));
				printf("Data is %i bytes long.\n", dataLength);
				if (dataLength > 0)
					printf("Data is %s\n", packet->data+sizeof(unsigned char)+sizeof(RakNetTime));

			}

			else if(packet->data[0] == ID_GET)
				cout << packet->data + 1 << endl;
			else if(packet->data[0] == ID_CHAT)
				cout << packet->data + 1 << endl;
			else
				cout << packet->data + 1 << endl;

			client->DeallocatePacket(packet);
		}
	}
	client->Shutdown(300);
	RakNetworkFactory::DestroyRakPeerInterface(client);
}



};



int main()
{
	return Server::main();
}
